﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;

namespace Vibstudio.X4NET
{
    /// <summary>
    /// ConvertibleExtensions class for basic types.
    /// </summary>
    public static class ConvertibleExtensions
    {
        /// <summary>
        /// Convert to correct type.
        /// </summary>
        /// <typeparam name="T">Type of object to convert.</typeparam>
        /// <param name="value">Object to convert.</param>
        /// <returns>Converted object.</returns>
        public static object ToObject<T>(this object value)
        {
            if (value == null) return default(T);

            if (typeof(T) == typeof(int)) return Convert.ToInt32(value);
            if (typeof(T) == typeof(long)) return Convert.ToInt64(value);
            if (typeof(T) == typeof(string)) return Convert.ToString(value);
            if (typeof(T) == typeof(bool)) return Convert.ToBoolean(value);
            if (typeof(T) == typeof(double)) return Convert.ToDouble(value);
            if (typeof(T) == typeof(DateTime)) return Convert.ToDateTime(value);

            return default(T);
        }

        /// <summary>
        /// Convert to correct type.
        /// </summary>
        /// <param name="type">Type of object whose type is to be converted.</param>
        /// <param name="input">Object whose type is to be converted.</param>
        /// <returns>Type of converted object.</returns>
        public static object ToObject(this Type type, object input)
        {
            object result = null;
            if (input == null || input == DBNull.Value) return null;

            if (type == typeof(int)) result = Convert.ToInt32(input);
            else if (type == typeof(long)) result = Convert.ToInt64(input);
            else if (type == typeof(string)) result = Convert.ToString(input);
            else if (type == typeof(bool)) result = Convert.ToBoolean(input);
            else if (type == typeof(double)) result = Convert.ToDouble(input);
            else if (type == typeof(DateTime)) result = Convert.ToDateTime(input);

            return result;
        }

        /// <summary>
        /// Check to see if can convert to appropriate type
        /// </summary>
        /// <param name="propertyInfo">Information of property to check.</param>
        /// <param name="value">Object to convert to.</param>
        /// <returns>True if the object can be converted to the property type.</returns>
        public static bool IsConvertibleToSameType(this PropertyInfo propertyInfo, object value)
        {
            return propertyInfo.ToSameType(value) != null;
        }

        /// <summary>
        /// Convert the val from string type to the same time as the property.
        /// </summary>
        /// <param name="propertyInfo">Property representing the type to convert to</param>
        /// <param name="value">val to convert</param>
        /// <returns>converted value with the same time as the property</returns>
        public static object ToSameType(this PropertyInfo propertyInfo, object value)
        {
            object convertedType = null;
            if (propertyInfo != null)
            {
                Type propertyType = Nullable.GetUnderlyingType(propertyInfo.PropertyType);
                bool isNullable = propertyType != null;
                if (!isNullable) { propertyType = propertyInfo.PropertyType; }
                bool isConvertible = value != null || isNullable;
                if (isConvertible) convertedType = value == null || Convert.IsDBNull(value) ? null : Convert.ChangeType(value, propertyType);
            }
            return convertedType;
        }


        /// <summary>
        /// Determine if the type of the property and the val are the same type.
        /// </summary>
        /// <param name="propertyInfo">Property info to check.</param>
        /// <param name="value">Value to check against property info.</param>
        /// <returns>True if the property and the value are of the same type.</returns>
        public static bool IsSameType(this PropertyInfo propertyInfo, object value)
        {
            // Quick Validation.
            if (propertyInfo.PropertyType == typeof(int) && value is int) { return true; }
            if (propertyInfo.PropertyType == typeof(bool) && value is bool) { return true; }
            if (propertyInfo.PropertyType == typeof(string) && value is string) { return true; }
            if (propertyInfo.PropertyType == typeof(double) && value is double) { return true; }
            if (propertyInfo.PropertyType == typeof(long) && value is long) { return true; }
            if (propertyInfo.PropertyType == typeof(float) && value is float) { return true; }
            if (propertyInfo.PropertyType == typeof(DateTime) && value is DateTime) { return true; }
            if (propertyInfo.PropertyType == value.GetType()) { return true; }

            return false;
        }

        public static string ToPath<T>(this T obj) where T : new()
        {
            StringBuilder pathBuilder = new StringBuilder();

            IEnumerable<PropertyInfo> propertyInfos = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
            foreach (PropertyInfo info in propertyInfos)
            {
                object propertyValue = info.GetValue(obj, null);
                if (propertyValue != null)
                {
                    pathBuilder.AppendFormat("{0}{1}", Defaults.PathSeparator, info.Name);
                }
            }

            return pathBuilder.ToString();
        }
    }
}